﻿// -----------------------------------------------------------------------
// <copyright file="Noise.cs" company="Image Processing Course Project">
// Created 13/12/2011
// </copyright>
// -----------------------------------------------------------------------

namespace ImageProcessing.IntermediateClasses
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using ImageProcessing.IntermediateClasses;
    using ImageProcessing.Utilities;

    /// <summary>
    /// Class that adds Noise to image
    /// </summary>
    public class Noise
    {
        /// <summary>
        /// Bitmap original image
        /// </summary>
        private Bitmap originalImage;

        /// <summary>
        /// Array length
        /// </summary>
        private int length;

        /// <summary>
        /// Array carrying pixels for perfect randomization
        /// </summary>
        private Index[] arrayIndex;

        /// <summary>
        /// Original image width
        /// </summary>
        private int width;

        /// <summary>
        /// Original image height
        /// </summary>
        private int height;

        /// <summary>
        /// Random number
        /// </summary>
        private Random random;

        /// <summary>
        /// Initializes a new instance of the Noise class
        /// </summary>
        /// <param name="originalImage">Bitmap original image</param>
        public Noise(Bitmap originalImage)
        {
            this.originalImage = originalImage;
            this.width = originalImage.Width;
            this.height = originalImage.Height;
            this.length = this.width * this.height;
            this.arrayIndex = new Index[this.width * this.height];
            this.random = new Random();
            this.ResetIndexArray();
        }

        /// <summary>
        /// Reseting Index array with the pixel coordinates
        /// </summary>
        public void ResetIndexArray()
        {
            int count = 0;
            try
            {
                for (int i = 0; i < this.width; i++)
                {
                    for (int j = 0; j < this.height; j++)
                    {
                        this.arrayIndex[count].X = i;
                        this.arrayIndex[count].Y = j;
                        count++;
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Perfect Randomization to select a unique pixel
        /// </summary>
        /// <returns>Random pixel coordinates</returns>
        public Index PerfectRandomization()
        {
            int randomIndex;
            try
            {
                randomIndex = this.random.Next(0, this.length);
                Index result = this.arrayIndex[randomIndex];
                this.arrayIndex[randomIndex] = this.arrayIndex[this.length - 1];
                this.length--;

                return result;
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Add noise to image using Gaussian distribution
        /// </summary>
        /// <param name="mu">Mean value</param>
        /// <param name="sigma">Sigma value</param>
        /// <param name="percentage">Percentage of image to add noise to</param>
        /// <returns>Bitmap noisy image</returns>
        public Bitmap GaussianAdditiveNoise(int mu, int sigma, double percentage)
        {
            int numberOfPixels;
            Index index;
            try
            {
                Colour.ColorsStruct[,] buffer = Colour.ConvertToStruct(this.originalImage);
                PostProcessing postProcessing = new PostProcessing();
                int red, green, blue;

                for (int i = 0; i < 255; i++)
                {
                    numberOfPixels = (int)(percentage * this.width * this.height * (Math.Exp(-(Math.Pow(i - mu, 2) / (2 * Math.Pow(sigma, 2)))) / Math.Sqrt(2 * Math.PI * Math.Pow(sigma, 2))));
                    for (int j = 0; j < numberOfPixels; j++)
                    {
                        index = this.PerfectRandomization();
                        red = i + buffer[index.X, index.Y].Red;
                        green = i + buffer[index.X, index.Y].Green;
                        blue = i + buffer[index.X, index.Y].Blue;
                        buffer[index.X, index.Y].Red = red;
                        buffer[index.X, index.Y].Green = green;
                        buffer[index.X, index.Y].Blue = blue;
                    }
                }

                buffer = postProcessing.Normalization(buffer, this.width, this.height);

                for (int i = 0; i < this.width; i++)
                {
                    for (int j = 0; j < this.height; j++)
                    {
                        this.originalImage.SetPixel(i, j, Color.FromArgb(buffer[i, j].Red, buffer[i, j].Green, buffer[i, j].Blue));
                    }
                }

                return this.originalImage;
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Add noise to image using Uniform distribution
        /// </summary>
        /// <param name="a">Minimum noise range</param>
        /// <param name="b">Maximum noise range</param>
        /// <param name="percentage">Percentage of image to add noise to</param>
        /// <returns>Bitmap noisy image</returns>
        public Bitmap UniformAdditiveNoise(int a, int b, double percentage)
        {
            int numberOfPixels = (int)(percentage * this.width * this.height * (double)(1 / ((double)b - (double)a)));
            Index index;
            try
            {
                Colour.ColorsStruct[,] buffer = Colour.ConvertToStruct(this.originalImage);
                PostProcessing postProcessing = new PostProcessing();
                int red, green, blue;

                for (int i = a; i < b; i++)
                {
                    for (int j = 0; j < numberOfPixels; j++)
                    {
                        index = this.PerfectRandomization();
                        red = i + buffer[index.X, index.Y].Red;
                        green = i + buffer[index.X, index.Y].Green;
                        blue = i + buffer[index.X, index.Y].Blue;
                        buffer[index.X, index.Y].Red = red;
                        buffer[index.X, index.Y].Green = green;
                        buffer[index.X, index.Y].Blue = blue;
                    }
                }

                buffer = postProcessing.Normalization(buffer, this.width, this.height);

                for (int i = 0; i < this.width; i++)
                {
                    for (int j = 0; j < this.height; j++)
                    {
                        this.originalImage.SetPixel(i, j, Color.FromArgb(buffer[i, j].Red, buffer[i, j].Green, buffer[i, j].Blue));
                    }
                }

                return this.originalImage;
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Add salt and pepper noise to image
        /// </summary>
        /// <param name="saltProb">Salt probability</param>
        /// <param name="pepperProb">Pepper Probability</param>
        /// <returns>Bitmap noisy image</returns>
        public Bitmap AddingSaltAndPepper(double saltProb, double pepperProb)
        {
            int saltNumber = (int)(saltProb * (this.width * this.height));
            int pepperNumber = (int)(pepperProb * (this.width * this.height));
            Index index;
            try
            {
                for (int i = 0; i < saltNumber; i++)
                {
                    index = this.PerfectRandomization();
                    this.originalImage.SetPixel(index.X, index.Y, Color.FromArgb(255, 255, 255));
                }

                this.length = this.width * this.height;
                this.ResetIndexArray();
                for (int i = 0; i < pepperNumber; i++)
                {
                    index = this.PerfectRandomization();
                    this.originalImage.SetPixel(index.X, index.Y, Color.FromArgb(0, 0, 0));
                }

                return this.originalImage;
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Add periodic noise to image
        /// </summary>
        /// <param name="a">Component Amplitude value</param>
        /// <param name="u0">Component Frequency in x direction</param>
        /// <param name="v0">Component Frequency in y direction</param>
        /// <param name="bX">Component Phase shift in x direction</param>
        /// <param name="bY">Component Phase shift in y direction</param>
        /// <returns>Bitmap noisy image</returns>
        public Bitmap PeriodicNoise(int a, double u0, double v0, double bX, double bY)
        {
            int noiseComponent;
            PostProcessing postProcessing = new PostProcessing();
            Colour.ColorsStruct[,] buffer = new Colour.ColorsStruct[this.originalImage.Width, this.originalImage.Height];
            try
            {
                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        noiseComponent = (int)(a * Math.Sin((2 * Math.PI * u0 * j / this.originalImage.Width + bX) + (2 * Math.PI * v0 * i / this.originalImage.Height + bY)));
                        buffer[j, i].Red = this.originalImage.GetPixel(j, i).R + noiseComponent;
                        buffer[j, i].Green = this.originalImage.GetPixel(j, i).G + noiseComponent;
                        buffer[j, i].Blue = this.originalImage.GetPixel(j, i).B + noiseComponent;
                    }
                }
                buffer = postProcessing.Normalization(buffer, this.originalImage.Width, this.originalImage.Height);
                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        this.originalImage.SetPixel(j, i, Color.FromArgb(buffer[j, i].Red, buffer[j, i].Green, buffer[j, i].Blue));
                    }
                }
                return this.originalImage;
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Struct carrying x and y coordinates of a pixel
        /// </summary>
        public struct Index
        {
            /// <summary>
            /// Pixel count in the x direction
            /// </summary>
            public int X;

            /// <summary>
            /// Pixel count in the y direction
            /// </summary>
            public int Y;
        }
    }
}
